From 8768db9cd59c574bc6f1eb0493367d1bc3b2d5e1 Mon Sep 17 00:00:00 2001 From: oliskoli Date: Wed, 5 Apr 2006 17:48:05 +0000 Subject: [PATCH] Add new format "garmin_txt". --- gpsbabel/garmin_txt.c | 1378 ++++++++++++++++++++++++++++ gpsbabel/reference/garmin_txt.txt | 96 ++ gpsbabel/reference/gdb-sample2.gdb | Bin 0 -> 6314 bytes 3 files changed, 1474 insertions(+) create mode 100644 gpsbabel/garmin_txt.c create mode 100644 gpsbabel/reference/garmin_txt.txt create mode 100644 gpsbabel/reference/gdb-sample2.gdb diff --git a/gpsbabel/garmin_txt.c b/gpsbabel/garmin_txt.c new file mode 100644 index 000000000..072f7c5ef --- /dev/null +++ b/gpsbabel/garmin_txt.c @@ -0,0 +1,1378 @@ +/* + + Support for MapSource Text Export (Tab delimited) files. + + Copyright (C) 2006 Olaf Klein, o.b.klein@t-online.de + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + + */ + +#include "defs.h" + +#include +#include +#include +#include +#include +#include "cet_util.h" +#include "csv_util.h" +#include "garmin_fs.h" +#include "garmin_tables.h" +#include "grtcirc.h" +#include "inifile.h" +#include "jeeps/gpsmath.h" +#include "strptime.h" + +#define MYNAME "garmin_txt" + +typedef struct gtxt_flags_s { + unsigned int metric:1; + unsigned int celsius:1; + unsigned int utc:1; + unsigned int enum_waypoints:1; + unsigned int route_header_written:1; + unsigned int track_header_written:1; +} gtxt_flags_t; + +static FILE *fin, *fout; +static route_head *current_trk, *current_rte; +static int waypoints; +static int routepoints; +static waypoint **wpt_a; +static int wpt_a_ct; +static int grid_index; +static int datum_index; +static char *datum_str; +static int current_line; +static char *date_time_format = NULL; +static int precision = 3; +static time_t utc_offs = 0; + +static gtxt_flags_t gtxt_flags; + +typedef enum { + waypt_header = 0, + rtept_header, + trkpt_header, + route_header, + track_header, + unknown_header +} header_type; + +#define MAX_HEADER_FIELDS 24 + +static char *header_lines[unknown_header + 1][MAX_HEADER_FIELDS]; +static int header_fields[unknown_header + 1][MAX_HEADER_FIELDS]; +static int header_ct[unknown_header + 1]; + +#define GARMIN_UNKNOWN_ALT 1.0e25f +#define DEFAULT_DISPLAY garmin_display_symbol_and_name +#define DEFAULT_DATE_FORMAT "dd/mm/yyyy" +#define DEFAULT_TIME_FORMAT "HH:mm:ss" + +/* macros */ + +#define IS_VALID_ALT(a) (((a) != unknown_alt) && ((a) < GARMIN_UNKNOWN_ALT)) +#define DUPSTR(a) (((a) != NULL) && ((a)[0] != 0)) ? xstrdup((a)) : NULL + +#define mod(y,x) (y) - (x) * floor((y)/(x)) +#define d2r(a) ((double)((a) * M_PI) / (double)180.0) +#define r2d(a) ((a) * (double)180 / M_PI) + +static char *opt_datum = NULL; +static char *opt_dist = NULL; +static char *opt_temp = NULL; +static char *opt_date_format = NULL; +static char *opt_time_format = NULL; +static char *opt_precision = NULL; +static char *opt_utc = NULL; + +static +arglist_t garmin_txt_args[] = { + {"date", &opt_date_format, "Read/Write date format (i.e. yyyy/mm/dd)", NULL, ARGTYPE_STRING, ARG_NOMINMAX}, + {"datum", &opt_datum, "GPS datum (def. WGS 84)", "WGS 84", ARGTYPE_STRING, ARG_NOMINMAX}, + {"dist", &opt_dist, "Distance unit [m=metric, s=statue]", "m", ARGTYPE_STRING, ARG_NOMINMAX}, + {"prec", &opt_precision, "Precision of coordinates", "3", ARGTYPE_INT, ARG_NOMINMAX}, + {"temp", &opt_temp, "Temperature unit [c=celsius, f=fahrenheit]", "c", ARGTYPE_STRING, ARG_NOMINMAX}, + {"time", &opt_time_format, "Read/Write time format (i.e. HH:mm:ss xx)", NULL, ARGTYPE_STRING, ARG_NOMINMAX}, + {"utc", &opt_utc, "Write timestamps with offset x to UTC time", NULL, ARGTYPE_INT, "-23", "+23"}, + ARG_TERMINATOR +}; + +typedef struct info_s +{ + double length; + time_t start; + time_t time; + double speed; + double total; + int count; + waypoint *prev_wpt; + waypoint *first_wpt; + waypoint *last_wpt; +} info_t; + +static info_t *route_info; +static int route_idx; +static info_t *cur_info; + +static char *headers[] = { + "Name\tDescription\tType\tPosition\tAltitude\tDepth\tProximity\tTemperature\t" + "Display Mode\tColor\tSymbol\tFacility\tCity\tState\tCountry\t" + "Date Modified\tLink\tCategories", + "Waypoint Name\tDistance\tLeg Length\tCourse", + "Position\tTime\tAltitude\tDepth\tLeg Length\tLeg Time\tLeg Speed\tLeg Course", + "Name\tLength\tCourse\tWaypoints\tLink", + "Name\tStart Time\tElapsed Time\tLength\tAverage Speed\tLink", + NULL +}; + +/* helpers */ + +static char * +get_option_val(char *option, char *def) +{ + char *c = (option != NULL) ? option : def; + return c; +} + +static void +init_date_and_time_format(void) +{ + struct tm tm; + char *c, *origin, *src, *dest, prev; + char buff[64], timef[32], datef[32]; + static char format[128]; + int offs, Y, H; + time_t time; + + memset(&datef, 0, sizeof(datef)); + memset(&timef, 0, sizeof(timef)); + + origin = get_option_val(opt_date_format, DEFAULT_DATE_FORMAT); + strncpy(buff, origin, sizeof(buff)); + + src = lrtrim(buff); + for (c = src; *c; c++) *c = toupper(*c); + + Y = 0; + prev = '\0'; + offs = (src - buff); + dest = datef; + + for (c = src; *c; c++, offs++) { + if (isalpha(*c)) { + switch(*c) { + case 'J': + case 'Y': + if (prev != 'Y') { + strcat(dest, "%y"); + dest += 2; + prev = 'Y'; + } + Y++; + if (Y > 2) *(dest-1) = 'Y'; + break; + case 'M': + if (prev != 'M') { + strcat(dest, "%m"); + dest += 2; + prev = 'M'; + } + break; + case 'D': + case 'T': + if (prev != 'D') { + strcat(dest, "%d"); + dest += 2; + prev = 'D'; + } + break; + default: + fatal(MYNAME ": Invalid character \"%c\" in date format!\n", origin[offs]); + } + } + else if (ispunct(*c)) { + *dest++ = *c; + } + } + + origin = get_option_val(opt_time_format, DEFAULT_TIME_FORMAT); + strncpy(buff, origin, sizeof(buff)); + + src = lrtrim(buff); + H = 0; + prev = '\0'; + offs = (src - buff); + dest = timef; + + for (c = src; *c; c++, offs++) { + if (isalpha(*c)) { + switch(*c) { + case 'S': + case 's': + if (prev != 'S') { + strcat(dest, "%S"); + dest += 2; + prev = 'S'; + } + break; + case 'M': + case 'm': + if (prev != 'M') { + strcat(dest, "%M"); + dest += 2; + prev = 'M'; + } + break; + case 'h': /* 12-hour-clock */ + if (prev != 'H') { + strcat(dest, "%l"); + dest += 2; + prev = 'H'; + } + else *(dest-1) = 'I'; + break; + case 'H': /* 24-hour-clock */ + if (prev != 'H') { + strcat(dest, "%l"); + dest += 2; + prev = 'H'; + } + else *(dest-1) = 'H'; + break; + case 'x': + if (prev != 'X') { + strcat(dest, "%P"); + dest += 2; + prev = 'X'; + } + else *(dest-1) = 'P'; + break; + case 'X': + if (prev != 'X') { + strcat(dest, "%p"); + dest += 2; + prev = 'X'; + } + else *(dest-1) = 'p'; + break; + default: + fatal(MYNAME ": Invalid character \"%c\" in time format!\n", origin[offs]); + } + } + else *dest++ = *c; + } + + strcpy(format, datef); + strcat(format, " "); + strcat(format, timef); + date_time_format = format; +} + +static double +distance(double lat1, double lon1, double lat2, double lon2) /* nearly MapSource compatible */ +{ + return r2d(gcdist(d2r(lat1), d2r(lon1), d2r(lat2), d2r(lon2))) * 111.32 * 1000.0; +} + +static double +course_rad(double lat1, double lon1, double lat2, double lon2) +{ + return mod(atan2(sin(lon1 - lon2) * cos(lat2), cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon1-lon2)), 2 * M_PI); +} + +static double +course_deg(double lat1, double lon1, double lat2, double lon2) +{ + return r2d(course_rad(d2r(lat1), d2r(lon1), d2r(lat2), d2r(lon2))); +} + +static double +waypt_distance(const waypoint *A, const waypoint *B) /* !!! from A to B !!! */ +{ + double dist = 0; + garmin_fs_p gmsd; + + if ((A == NULL) || (B == NULL)) return 0; + + gmsd = GMSD_FIND(A); + if ((gmsd != NULL) && (gmsd->ilinks != NULL)) + { + garmin_ilink_t *link = gmsd->ilinks; + + dist = distance(A->latitude, A->longitude, link->lat, link->lon); + while (link->next != NULL) + { + garmin_ilink_t *prev = link; + link = link->next; + dist += distance(prev->lat, prev->lon, link->lat, link->lon); + } + dist += distance(link->lat, link->lon, B->latitude, B->longitude); + } else + { + dist = distance(A->latitude, A->longitude, B->latitude, B->longitude); + } + return dist; +} + +static void +convert_datum(waypoint *wpt, const int to_internal_wgs84, double *dest_lat, double *dest_lon) +{ + double alt; + + if (datum_index == 118 /* WGS 84 */) { + if (to_internal_wgs84 == 0) { + *dest_lat = wpt->latitude; + *dest_lon = wpt->longitude; + } + return; + } + + if (to_internal_wgs84) { /* convert the waypoint himself */ + GPS_Math_Known_Datum_To_WGS84_M(wpt->latitude, wpt->longitude, 0.0, + &wpt->latitude, &wpt->longitude, &alt, datum_index); + } else + GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0.0, + dest_lat, dest_lon, &alt, datum_index); +} + +/* WRITER *****************************************************************/ + +/* Waypoint preparation */ + +static void +enum_waypt_cb(const waypoint *wpt) +{ + garmin_fs_p gmsd; + int wpt_class; + + gmsd = GMSD_FIND(wpt); + wpt_class = GMSD_GET(wpt_class, 0); + if (wpt_class < 0x80) + { + int i; + + if (gtxt_flags.enum_waypoints) /* enumerate only */ + { + waypoints++; + return; + } + for (i = 0; i < wpt_a_ct; i++) { /* check for duplicates */ + waypoint *tmp = wpt_a[i]; + if (case_ignore_strcmp(tmp->shortname, wpt->shortname) == 0) + { + wpt_a[i] = (waypoint *)wpt; + waypoints--; + return; + + } + } + wpt_a[wpt_a_ct++] = (waypoint *)wpt; + } + +} + +static int +sort_waypt_cb(const void *a, const void *b) +{ + const waypoint *wa = *(waypoint **)a; + const waypoint *wb = *(waypoint **)b; + + return case_ignore_strcmp(wa->shortname, wb->shortname); +} + + +/* common route and track pre-work */ + +static void +prework_hdr_cb(const route_head *rte) +{ + cur_info = &route_info[route_idx]; + cur_info->prev_wpt = NULL; + cur_info->length = 0; + cur_info->time = 0; +} + +static void +prework_tlr_cb(const route_head *rte) +{ + cur_info->last_wpt = cur_info->prev_wpt; + route_idx++; +} + +static void +prework_wpt_cb(const waypoint *wpt) +{ + waypoint *prev = cur_info->prev_wpt; + + if (prev != NULL) { + cur_info->time += (wpt->creation_time - prev->creation_time); + cur_info->length += waypt_distance(prev, wpt); + } + else { + cur_info->first_wpt = (waypoint *)wpt; + cur_info->start = wpt->creation_time; + } + cur_info->prev_wpt = (waypoint *)wpt; + cur_info->count++; + routepoints++; +} + + +/* output helpers */ + +static void +print_position(const waypoint *wpt) +{ + int deg; + double min; + char num[64]; + double lat, lon; + + convert_datum((waypoint *)wpt, 0, &lat, &lon); + + deg = fabs(lat); + min = (double)60.0 * (fabs(lat) - deg); + snprintf(num, sizeof(num), "%0*.*f", precision + 3, precision, min); + if (atoi(num) == 60) { + deg++; + min = 0; + } + fprintf(fout, "%c%d %0*.*f ", lat < 0.0 ? 'S' : 'N', deg, precision + 3, precision, min); + + deg = fabs(lon); + min = (double)60.0 * (fabs(lon) - deg); + snprintf(num, sizeof(num), "%0*.*f", precision + 3, precision, min); + if (atoi(num) == 60) { + deg++; + min = 0; + } + fprintf(fout, "%c%d %0*.*f\t", lon < 0.0 ? 'W' : 'E', deg, precision + 3, precision, min); +} + +static void +print_date_and_time(const time_t time, const int time_only) +{ + struct tm tm, tm2; + char tbuf[32]; + + if (time_only) { + tm = *gmtime(&time); + snprintf(tbuf, sizeof(tbuf), "%d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); + fprintf(fout, "%s", tbuf); + } + else if (time != 0) { + if (gtxt_flags.utc) { + time_t t = time + utc_offs; + tm = *gmtime(&t); + } + else + tm = *localtime(&time); + strftime(tbuf, sizeof(tbuf), date_time_format, &tm); + fprintf(fout, "%s ", tbuf); + } + fprintf(fout, "\t"); +} + +static void +print_categories(gbuint16 categories) +{ + int i, count; + char *c; + + if (categories == 0) return; + + count = 0; + for (i = 0; i < 16; i++) { + if ((categories & 1) != 0) { + if (global_opts.inifile != NULL) { + char key[3]; + snprintf(key, sizeof(key), "%d", i + 1); + c = inifile_readstr(global_opts.inifile, GMSD_SECTION_CATEGORIES, key); + } + else c = NULL; + + fprintf(fout, "%s", (count++ > 0) ? "," : ""); + if (c == NULL) + fprintf(fout, "Category %d", i+1); + else + fprintf(fout, "%s", c); + + } + categories = categories >> 1; + } +} + +static void +print_course(const waypoint *A, const waypoint *B) /* seems to be okay */ +{ + if ((A != NULL) && (B != NULL) && (A != B)) { + int course; + char buff[32]; + course = si_round((double)360 - course_deg(A->latitude, A->longitude, B->latitude, B->longitude)); + if (course >= 360) { + course -= 360; + } + cet_fprintf(fout, &cet_cs_vec_cp1252, "%d%c true", course, 0xB0); + } +} + +static void +print_distance(const double distance, const int no_scale, const int with_tab) +{ + double dist = distance; + + if (gtxt_flags.metric == 0) { + dist = dist / (double)0.3048; + + if ((dist < 5280) || no_scale) + fprintf(fout, "%.f ft", dist); + else { + dist = (distance / 1609.344); + if (dist < (double)100) + fprintf(fout, "%.1f mi", dist); + else + fprintf(fout, "%d mi", si_round(dist)); + } + } + else + { + if ((dist < 1000) || no_scale) + fprintf(fout, "%.f m", dist); + else { + dist = dist / (double)1000.0; + if (dist < (double)100) + fprintf(fout, "%.1f km", dist); + else + fprintf(fout, "%d km", si_round(dist)); + } + } + if (with_tab) fprintf(fout, "\t"); +} + +static void +print_speed(double *distance, time_t *time) +{ + int idist; + double dist = *distance; + char *unit; + + if (!gtxt_flags.metric) { + dist = dist / (double)1.609344; + unit = "mph"; + } + else unit = "kph"; + idist = si_round(dist); + + if ((time != 0) && (idist > 0)) { + double speed = dist / (double)*time * (double)3.600; + int ispeed = si_round(speed); + + if (speed < (double)0.01) + fprintf(fout, "0 %s", unit); + else if (ispeed < 2) + fprintf(fout, "%.1f %s", speed, unit); + else + fprintf(fout, "%d %s", ispeed, unit); + } + else + fprintf(fout, "0 %s", unit); + fprintf(fout, "\t"); +} + + +/* main cb's */ + +static void +write_waypt(const waypoint *wpt) +{ + unsigned char wpt_class; + garmin_fs_p gmsd; + char *wpt_type; + char *dspl_mode; + char *country; + double x; + int i, icon, dynamic; + char *icon_descr; + + gmsd = GMSD_FIND(wpt); + + i = GMSD_GET(display, 0); + if (i > GT_DISPLAY_MODE_MAX) i = 0; + dspl_mode = gt_display_mode_names[i]; + + wpt_class = GMSD_GET(wpt_class, 0); + if (wpt_class <= gt_waypt_class_map_line) + wpt_type = gt_waypt_class_names[wpt_class]; + else + wpt_type = gt_waypt_class_names[0]; + + fprintf(fout, "Waypoint\t%s\t", (wpt->shortname) ? wpt->shortname : ""); + if (wpt_class <= gt_waypt_class_airport_ndb) { + char *temp = wpt->notes; + if (temp == NULL) { + if (wpt->description && (strcmp(wpt->description, wpt->shortname) != 0)) + temp = wpt->description; + else + temp = ""; + } + fprintf(fout, "%s\t", temp); + } + else + fprintf(fout, "\t"); + fprintf(fout, "%s\t", wpt_type); + + print_position(wpt); + + if IS_VALID_ALT(wpt->altitude) + print_distance(wpt->altitude, 1, 0); + fprintf(fout, "\t"); + + x = GMSD_GET(depth, unknown_alt); + if (x != unknown_alt) + print_distance(x, 1, 0); + fprintf(fout, "\t"); + + x = GMSD_GET(proximity, unknown_alt); + if (x != unknown_alt) + print_distance(x, 0, 0); + fprintf(fout, "\t"); + + x = GMSD_GET(temperature, unknown_alt); + if (x != unknown_alt) { + if (gtxt_flags.celsius) + fprintf(fout, "%.f C", x); + else + fprintf(fout, "%.f F", (x * 1.8) + 32); + } + fprintf(fout, "\t%s\t", dspl_mode); + + fprintf(fout, "Unknown\t"); /* Color is fixed: Unknown */ + + icon = GMSD_GET(icon, -1); + if (icon == -1) { + icon = gt_find_icon_number_from_desc(wpt->icon_descr, GDB); + } + icon_descr = gt_find_desc_from_icon_number(icon, GDB, &dynamic); + fprintf(fout, "%s\t", icon_descr); + if (dynamic) xfree(icon_descr); + + fprintf(fout, "%s\t", GMSD_GET(facility, "")); + fprintf(fout, "%s\t", GMSD_GET(city, "")); + fprintf(fout, "%s\t", GMSD_GET(state, "")); + country = gt_get_icao_country(GMSD_GET(cc, "")); + fprintf(fout, "%s\t", (country != NULL) ? country : ""); + print_date_and_time(wpt->creation_time, 0); + fprintf(fout, "%s\t", wpt->url ? wpt->url : ""); + print_categories(GMSD_GET(category, 0)); + + fprintf(fout, "\r\n"); +} + +static void +route_disp_hdr_cb(const route_head *rte) +{ + current_trk = (route_head *)rte; + cur_info = &route_info[route_idx]; + cur_info->prev_wpt = NULL; + cur_info->total = 0; + if (rte->rte_waypt_ct <= 0) return; + + if (!gtxt_flags.route_header_written) { + gtxt_flags.route_header_written = 1; + fprintf(fout, "\r\n\r\nHeader\t%s\r\n", headers[route_header]); + } + + fprintf(fout, "\r\nRoute\t%s\t", current_trk->rte_name ? current_trk->rte_name : ""); + print_distance(cur_info->length, 0, 1); + print_course(cur_info->first_wpt, cur_info->last_wpt); + fprintf(fout, "\t%d waypoints\t", cur_info->count); + fprintf(fout, "%s\r\n", rte->rte_url ? rte->rte_url : ""); + fprintf(fout, "\r\nHeader\t%s\r\n\r\n", headers[rtept_header]); +} + +static void +route_disp_tlr_cb(const route_head *rte) +{ + route_idx++; +} + +static void +route_disp_wpt_cb(const waypoint *wpt) +{ + waypoint *prev = cur_info->prev_wpt; + + fprintf(fout, "Route Waypoint\t"); + fprintf(fout, "%s\t", wpt->shortname); + + if (prev != NULL) + { + double dist = waypt_distance(prev, wpt); + cur_info->total += dist; + print_distance(cur_info->total, 0, 1); + print_distance(dist, 0, 1); + print_course(prev, wpt); + } + else + print_distance(0, 1, 0); + + fprintf(fout, "\r\n"); + + cur_info->prev_wpt = (waypoint *)wpt; +} + +static void +track_disp_hdr_cb(const route_head *track) +{ + cur_info = &route_info[route_idx]; + cur_info->prev_wpt = NULL; + cur_info->total = 0; + current_trk = (route_head *)track; + if (track->rte_waypt_ct <= 0) return; + + if (!gtxt_flags.track_header_written) { + gtxt_flags.track_header_written = 1; + fprintf(fout, "\r\n\r\nHeader\t%s\r\n", headers[track_header]); + } + + fprintf(fout, "\r\nTrack\t%s\t", current_trk->rte_name ? current_trk->rte_name : ""); + print_date_and_time(cur_info->start, 0); + print_date_and_time(cur_info->time, 1); + print_distance(cur_info->length, 0, 1); + print_speed(&cur_info->length, &cur_info->time); + fprintf(fout, "\r\n\r\nHeader\t%s\r\n\r\n", headers[trkpt_header]); +} + +static void +track_disp_tlr_cb(const route_head *track) +{ + route_idx++; +} + +static void +track_disp_wpt_cb(const waypoint *wpt) +{ + waypoint *prev = cur_info->prev_wpt; + time_t delta; + double dist; + + fprintf(fout, "Trackpoint\t"); + + print_position(wpt); + print_date_and_time(wpt->creation_time, 0); + if IS_VALID_ALT(wpt->altitude) + print_distance(wpt->altitude, 1, 0); + fprintf(fout, "\t0.0 %s", (gtxt_flags.metric) ? "m" : "ft"); + if (prev != NULL) { + fprintf(fout, "\t"); + delta = wpt->creation_time - prev->creation_time; + dist = distance(prev->latitude, prev->longitude, wpt->latitude, wpt->longitude); + print_distance(dist, 0, 1); + print_date_and_time(delta, 1); + print_speed(&dist, &delta); + print_course(prev, wpt); + } + fprintf(fout, "\r\n"); + + cur_info->prev_wpt = (waypoint *)wpt; +} + +/******************************************************************************* +* %%% global callbacks called by gpsbabel main process %%% * +*******************************************************************************/ + +static void +garmin_txt_wr_init(const char *fname) +{ + char *temp; + + memset(>xt_flags, 0, sizeof(gtxt_flags)); + + fout = xfopen(fname, "wb", MYNAME); + grid_index = 1; + + gtxt_flags.metric = (toupper(*get_option_val(opt_dist, "m")) == 'M'); + gtxt_flags.celsius = (toupper(*get_option_val(opt_temp, "c")) == 'C'); + init_date_and_time_format(); + if (opt_precision) { + precision = atoi(opt_precision); + is_fatal(precision < 0, MYNAME ": Invalid precision (%s)!", opt_precision); + } + datum_str = get_option_val(opt_datum, NULL); + datum_index = GPS_Lookup_Datum_Index(datum_str); + is_fatal(datum_index < 0, MYNAME ": Invalid or unknown gps datum (%s)!", datum_str); + + if (opt_utc != NULL) { + if (case_ignore_strcmp(opt_utc, "utc") == 0) + utc_offs = 0; + else + utc_offs = atoi(opt_utc); + utc_offs *= (60 * 60); + gtxt_flags.utc = 1; + } +} + +static void +garmin_txt_wr_deinit(void) +{ + fclose(fout); +} + +static void +garmin_txt_write(void) +{ + cet_fprintf(fout, &cet_cs_vec_cp1252, "Grid\tLat/Lon hddd%cmm.mmm'\r\n", 0xB0); + fprintf(fout, "Datum\t%s\r\n\r\n", datum_str); + + waypoints = 0; + gtxt_flags.enum_waypoints = 1; /* enum all waypoints */ + waypt_disp_all(enum_waypt_cb); + route_disp_all(NULL, NULL, enum_waypt_cb); + gtxt_flags.enum_waypoints = 0; + + if (waypoints > 0) { + int i; + + wpt_a_ct = 0; + wpt_a = (waypoint **)xcalloc(waypoints, sizeof(*wpt_a)); + waypt_disp_all(enum_waypt_cb); + route_disp_all(NULL, NULL, enum_waypt_cb); + qsort(wpt_a, waypoints, sizeof(*wpt_a), sort_waypt_cb); + + fprintf(fout, "Header\t%s\r\n\r\n", headers[waypt_header]); + for (i = 0; i < waypoints; i++) + { + waypoint *wpt = wpt_a[i]; + write_waypt(wpt); + } + xfree(wpt_a); + + route_idx = 0; + route_info = xcalloc(route_count(), sizeof(struct info_s)); + routepoints = 0; + route_disp_all(prework_hdr_cb, prework_tlr_cb, prework_wpt_cb); + if (routepoints > 0) + { + route_idx = 0; + route_disp_all(route_disp_hdr_cb, route_disp_tlr_cb, route_disp_wpt_cb); + } + xfree(route_info); + } + + route_idx = 0; + route_info = xcalloc(track_count(), sizeof(struct info_s)); + routepoints = 0; + track_disp_all(prework_hdr_cb, prework_tlr_cb, prework_wpt_cb); + + if (routepoints > 0) { + route_idx = 0; + track_disp_all(track_disp_hdr_cb, track_disp_tlr_cb, track_disp_wpt_cb); + } + xfree(route_info); +} + +/* READER *****************************************************************/ + +/* helpers */ + +static void +free_header(const header_type ht) +{ + int i; + + for (i = 0; i < MAX_HEADER_FIELDS; i++) { + char *c = header_lines[ht][i]; + if (c != NULL) { + xfree(c); + header_lines[ht][i] = NULL; + } + } + header_ct[ht] = 0; + memset(header_fields[ht], 0, sizeof(header_fields[ht])); +} + +/* data parsers */ + +static void +parse_position(const char *str, waypoint *wpt) +{ + double lat, lon; + unsigned char lathemi, hemilon; + int deg_lat, deg_lon, min_lat, min_lon; + + switch(grid_index) { + case 0: + sscanf(str, "%c%lf %c%lf", &lathemi, &lat, &hemilon, &lon); + break; + case 1: + sscanf(str, "%c%d %lf %c%d %lf", &lathemi, °_lat, &lat, &hemilon, °_lon, &lon); + lat = (double)deg_lat + (lat / (double)60); + lon = (double)deg_lon + (lon / (double)60); + break; + case 2: + sscanf(str, "%c%d %d %lf %c%d %d %lf", &lathemi, °_lat, &min_lat, &lat, &hemilon, °_lon, &min_lon, &lon); + lat = (double)deg_lat + ((double)min_lat / (double)60) + (lat / (double)3600.0); + lon = (double)deg_lon + ((double)min_lon / (double)60) + (lon / (double)3600.0); + break; + } + + if (lathemi == 'S') + wpt->latitude = -lat; + else + wpt->latitude = lat; + + if (hemilon == 'W') + wpt->longitude = -lon; + else + wpt->longitude = lon; +} + +static int +parse_distance(const char *str, double *value) +{ + double x; + char *buff; + + if ((str == NULL) || (*str == '\0')) return 0; + + buff = xmalloc(strlen(str) + 1); + sscanf(str, "%lf %s", &x, buff); + + if (case_ignore_strcmp(buff, "km") == 0) { + *value = x * (double)1000; + } + else if (case_ignore_strcmp(buff, "m") == 0) { /* meters */ + *value = x; + } + else if (case_ignore_strcmp(buff, "ft") == 0) { /* feet */ + *value = x * (double)0.3048; + } + else if (case_ignore_strcmp(buff, "nm") == 0) { /* mile (nautical / geographical) */ + *value = x * (double)1852.0; + } + else if (case_ignore_strcmp(buff, "mi") == 0) { /* mile (statute) */ + *value = x * (double)1609.344; + } + else if (case_ignore_strcmp(buff, "fa") == 0) { /* fathom */ + *value = x * (double)1.8288; + } + else + fatal(MYNAME ": Unknown distance unit \"%s\" at line %d!\n", str, current_line); + + xfree(buff); + return 1; +} + +static int +parse_date_and_time(char *str, time_t *value) +{ + struct tm tm; + char *cerr, *cin; + + cin = lrtrim(str); + if (*cin == '\0') return 0; + + cerr = strptime(cin, date_time_format, &tm); + if (cerr == NULL) { + cerr = strptime(cin, "%m/%d/%Y %I:%M:%S %p", &tm); + is_fatal(cerr == NULL, MYNAME ": Invalid date or/and time \"%s\" at line %d!", cin, current_line); + } + +// printf(MYNAME "_parse_date_and_time: %02d.%02d.%04d, %02d:%02d:%02d\n", +// tm.tm_mday, tm.tm_mon+1, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec); + + *value = mktime(&tm); + return 1; +} + +static gbuint16 +parse_categories(const char *str) +{ + char buff[256]; + gbuint16 val; + gbuint16 res = 0; + char *cin, *cx; + + if (*str == '\0') return 0; + + strncpy(buff, str, sizeof(buff)); + cin = lrtrim(buff); + if (*cin == '\0') return 0; + + strcat(cin, ","); + + while ((cx = strchr(cin, ','))) { + *cx++ = '\0'; + cin = lrtrim(cin); + if (*cin != '\0') { + if (!garmin_fs_convert_category(cin, &val)) + warning(MYNAME ": Unable to convert category \"%s\" at line %d!", cin, current_line); + else + res = res | val; + } + cin = cx; + } + return res; +} + +static int +parse_temperature(const char *str, double *temperature) +{ + double value; + unsigned char unit; + + if ((str == NULL) || (*str == '\0')) return 0; + + if (sscanf(str, "%lf %c", &value, &unit) == 2) { + unit = toupper(unit); + switch(unit) { + case 'C': *temperature = value; break; + case 'F': *temperature = (value - 32) / 1.8; break; + default: + fatal(MYNAME ": Unknown temperature unit \"%c\" at line %d!\n", unit, current_line); + } + return 1; + } + else + fatal(MYNAME ": Invalid temperature \"%s\" at line %d!\n", str, current_line); +} + +static void +parse_header(void) +{ + char *str; + int column = -1; + + free_header(unknown_header); + + while ((str = csv_lineparse(NULL, "\t", "", column++))) { + header_lines[unknown_header][column] = xstrdup(str); + str = header_lines[unknown_header][column]; + while (*str) *str++ = toupper(*str); + header_ct[unknown_header]++; + if (header_ct[unknown_header] >= MAX_HEADER_FIELDS) break; + } +} + +static int +parse_display(const char *str, int *val) +{ + gt_display_modes_e i; + + if ((str == NULL) || (*str == '\0')) return 0; + + for (i = GT_DISPLAY_MODE_MIN; i <= GT_DISPLAY_MODE_MAX; i++) { + if (case_ignore_strcmp(str, gt_display_mode_names[i]) == 0) { + *val = i; + return 1; + } + } + warning(MYNAME ": Unknown display mode \"%s\" at line %d.\n", str, current_line); +} + +static void +bind_fields(const header_type ht) +{ + int i; + char *fields, *c; + + is_fatal((grid_index < 0) || (datum_index < 0), MYNAME ": Incomplete or invalid file header!"); + + if (header_ct[unknown_header] <= 0) return; + free_header(ht); + + /* make a copy of headers[ht], uppercase, replace "\t" with "\0" */ + + i = strlen(headers[ht]); + fields = xmalloc(i + 2); + strcpy(fields, headers[ht]); + strcat(fields, "\t"); + c = fields; + while (*c) *c++ = toupper(*c); + c = fields; + while ((c = strchr(c, '\t'))) *c++ = '\0'; + + for (i = 0; i < header_ct[unknown_header]; i++) { + char *name; + int field_no; + name = header_lines[ht][i] = header_lines[unknown_header][i]; + header_lines[unknown_header][i] = NULL; + + c = fields; + field_no = 1; + while (c != NULL) { + if (strcmp(c, name) == 0) { + header_fields[ht][i] = field_no; +#if 0 + printf("Binding field \"%s\" to internal number %d (%d,%d)\n", name, field_no, ht, i); +#endif + break; + } + field_no++; + c = c + strlen(c) + 1; + } + } + header_ct[unknown_header] = 0; + xfree(fields); +} + +static void +parse_grid(void) +{ + char *str = csv_lineparse(NULL, "\t", "", 1); + if (str != NULL) { + if (strstr(str, "dd.ddddd") != 0) grid_index = 0; + else if (strstr(str, "mm.mmm") != 0) grid_index = 1; + else if (strstr(str, "mm'ss.s") != 0) grid_index = 2; + else fatal(MYNAME ": Unsupported grid (%s)!\n", str); + } + else + fatal(MYNAME ": Missing grid headline!\n"); +} + +static void +parse_datum(void) +{ + char *str = csv_lineparse(NULL, "\t", "", 1); + + if (str != NULL) { + datum_index = GPS_Lookup_Datum_Index(str); + is_fatal(datum_index < 0, MYNAME ": Unsupported GPS datum \"%s\"!", str); + } + else + fatal(MYNAME ": Missing GPS datum headline!\n"); +} + +static void +parse_waypoint(void) +{ + char *str; + int column = -1; + waypoint *wpt; + garmin_fs_p gmsd = NULL; + + bind_fields(waypt_header); + + wpt = waypt_new(); + gmsd = garmin_fs_alloc(-1); + fs_chain_add(&wpt->fs, (format_specific_data *) gmsd); + + while ((str = csv_lineparse(NULL, "\t", "", column++))) + { + int i, dynamic; + double d; + int field_no = header_fields[waypt_header][column]; + + switch(field_no) { + case 1: wpt->shortname = DUPSTR(str); break; + case 2: wpt->notes = DUPSTR(str); break; + case 3: + for (i = 0; i <= gt_waypt_class_map_line; i++) { + if (case_ignore_strcmp(str, gt_waypt_class_names[i]) == 0) { + GMSD_SET(wpt_class, i); + break; + } + } + break; + case 4: parse_position(str, wpt); break; + case 5: if (parse_distance(str, &d)) wpt->altitude = d; break; + case 6: if (parse_distance(str, &d)) GMSD_SET(depth, d); break; + case 7: if (parse_distance(str, &d)) GMSD_SET(proximity, d); break; + case 8: if (parse_temperature(str, &d)) GMSD_SET(temperature, d); break; + case 9: if (parse_display(str, &i)) GMSD_SET(display, i); break; + case 10: break; /* skip color */ + case 11: + i = gt_find_icon_number_from_desc(str, GDB); + GMSD_SET(icon, i); + wpt->icon_descr = gt_find_desc_from_icon_number(i, GDB, &dynamic); + wpt->wpt_flags.icon_descr_is_dynamic = dynamic; + break; + case 12: GMSD_SETSTR(facility, str); break; + case 13: GMSD_SETSTR(city, str); break; + case 14: GMSD_SETSTR(state, str); break; + case 15: GMSD_SETSTR(cc, gt_get_icao_cc(str, wpt->shortname)); break; + case 16: parse_date_and_time(str, &wpt->creation_time); break; + case 17: wpt->url = DUPSTR(str); break; + case 18: GMSD_SET(category, parse_categories(str)); break; + default: break; + } + } + convert_datum(wpt, 1, NULL, NULL); + waypt_add(wpt); +} + +static void +parse_route_header(void) +{ + char *str; + int column = -1; + route_head *rte; + + rte = route_head_alloc(); + + bind_fields(route_header); + while ((str = csv_lineparse(NULL, "\t", "", column++))) { + int field_no = header_fields[route_header][column]; + switch(field_no) { + case 1: rte->rte_name = DUPSTR(str); break; + case 5: rte->rte_url = DUPSTR(str); break; + } + } + route_add_head(rte); + current_rte = rte; +} + +static void +parse_track_header(void) +{ + char *str; + int column = -1; + route_head *trk; + + bind_fields(track_header); + trk = route_head_alloc(); + while ((str = csv_lineparse(NULL, "\t", "", column++))) { + int field_no = header_fields[track_header][column]; + switch(field_no) { + case 1: trk->rte_name = DUPSTR(str); break; + case 6: trk->rte_url = DUPSTR(str); break; + } + } + track_add_head(trk); + current_trk = trk; +} + +static void +parse_route_waypoint(void) +{ + char *str; + int column = -1; + waypoint *wpt = NULL; + + bind_fields(rtept_header); + + while ((str = csv_lineparse(NULL, "\t", "", column++))) { + int field_no = header_fields[rtept_header][column]; + switch(field_no) { + case 1: + is_fatal((*str == '\0'), MYNAME ": Route waypoint without name at line %d!\n", current_line); + wpt = find_waypt_by_name(str); + is_fatal((wpt == NULL), MYNAME ": Route waypoint \"%s\" not in waypoint list (line %d)!\n", str, current_line); + wpt = waypt_dupe(wpt); + break; + } + } + if (wpt != NULL) + route_add_wpt(current_rte, wpt); +} + +static void +parse_track_waypoint(void) +{ + char *str; + int column = -1; + waypoint *wpt; + + bind_fields(trkpt_header); + wpt = waypt_new(); + + while ((str = csv_lineparse(NULL, "\t", "", column++))) { + int field_no = header_fields[trkpt_header][column]; + switch(field_no) { + case 1: parse_position(str, wpt); break; + case 2: parse_date_and_time(str, &wpt->creation_time); break; + case 3: parse_distance(str, &wpt->altitude); break; + } + } + convert_datum(wpt, 1, NULL, NULL); + route_add_wpt(current_trk, wpt); +} + +/***************************************************************/ + +static void +garmin_rd_init(const char *fname) +{ + memset(>xt_flags, 0, sizeof(gtxt_flags)); + + fin = xfopen(fname, "rb", MYNAME); + memset(&header_ct, 0, sizeof(header_ct)); + + datum_index = -1; + grid_index = -1; + + init_date_and_time_format(); +} + +static void +garmin_rd_deinit(void) +{ + header_type h; + + for (h = waypt_header; h <= unknown_header; h++) { + free_header(h); + } + fclose(fin); +} + +static void +garmin_txt_read(void) +{ + char buff[1024]; + + current_line = 0; + + while ((fgets(buff, sizeof(buff), fin))) { + char *cin; + + current_line++; + cin = lrtrim(buff); + if (*cin == '\0') continue; + + cin = csv_lineparse(cin, "\t", "", 0); + + if (cin == NULL) continue; + else if (case_ignore_strcmp(cin, "Header") == 0) parse_header(); + else if (case_ignore_strcmp(cin, "Grid") == 0) parse_grid(); + else if (case_ignore_strcmp(cin, "Datum") == 0) parse_datum(); + else if (case_ignore_strcmp(cin, "Waypoint") == 0) parse_waypoint(); + else if (case_ignore_strcmp(cin, "Route Waypoint") == 0) parse_route_waypoint(); + else if (case_ignore_strcmp(cin, "Trackpoint") == 0) parse_track_waypoint(); + else if (case_ignore_strcmp(cin, "Route") == 0) parse_route_header(); + else if (case_ignore_strcmp(cin, "Track") == 0) parse_track_header(); + else if (case_ignore_strcmp(cin, "Map") == 0) /* do nothing */ ; + else + fatal(MYNAME ": Unknwon identifier (%s) at line %d!\n", cin, current_line); + + /* flush pending data */ + while (csv_lineparse(NULL, "\t", "", 0)); + } +} + +ff_vecs_t garmin_txt_vecs = { + ff_type_file, + FF_CAP_RW_ALL, + garmin_rd_init, + garmin_txt_wr_init, + garmin_rd_deinit, + garmin_txt_wr_deinit, + garmin_txt_read, + garmin_txt_write, + NULL, + garmin_txt_args, + CET_CHARSET_MS_ANSI, 0 +}; diff --git a/gpsbabel/reference/garmin_txt.txt b/gpsbabel/reference/garmin_txt.txt new file mode 100644 index 000000000..c868a9640 --- /dev/null +++ b/gpsbabel/reference/garmin_txt.txt @@ -0,0 +1,96 @@ +Grid Lat/Lon hddd°mm.mmm' +Datum WGS 84 + +Header Name Description Type Position Altitude Depth Proximity Temperature Display Mode Color Symbol Facility City State Country Date Modified Link Categories + +Waypoint 001 Map Line N50 29.556188732 E12 06.325848140 Symbol Unknown Waypoint 28/03/2006 00:10:37 +Waypoint 002 Map Intersection N50 29.556188732 E12 06.325848140 Symbol Unknown Waypoint 28/03/2006 00:10:37 +Waypoint 003 Map Intersection N50 29.656610638 E12 06.307823695 Symbol Unknown Waypoint 28/03/2006 00:10:37 +Waypoint 004 Map Line N50 29.630036652 E12 06.366030984 Symbol Unknown Waypoint 28/03/2006 00:10:37 +Waypoint 005 Map Line N50 29.630036652 E12 06.366030984 Symbol Unknown Waypoint 28/03/2006 00:10:37 +Waypoint 006 Map Intersection N50 29.602537304 E12 06.426270045 Symbol Unknown Waypoint 28/03/2006 00:10:37 +Waypoint 007 Map Line N50 29.619586095 E12 06.429106481 Symbol Unknown Waypoint 28/03/2006 00:10:37 +Waypoint ED_X Dummy airport (Germany) Airport N51 53.627961650 E12 58.676564991 Symbol & Name Unknown Airport FAC1 CITY1 Germany 28/03/2006 01:38:07 +Waypoint GC_X Dummy airport (Spain) Airport N38 37.919719778 W3 10.443304181 Symbol & Name Unknown Airport FAC2 CITY2 Canary Island 28/03/2006 01:42:01 +Waypoint Jahnstrasse Jahnstrasse 11 User Waypoint N50 29.619998485 E12 06.429000869 Symbol & Description Unknown Flag, Red 31/03/2006 21:48:22 +Waypoint LF_X Dummy airport (France) Airport N46 23.256332763 E3 29.896638617 Symbol & Name Unknown Airport FAC3 CITY3 France 28/03/2006 01:40:32 +Waypoint Liebknechtstrasse Liebknechtstrasse 90 User Waypoint N50 29.630041681 E12 06.366015896 Symbol & Name Unknown Waypoint 31/03/2006 21:49:30 +Waypoint LI_X Dummy airport (Italy) Airport N43 18.873018846 E12 09.693240859 Symbol & Name Unknown Heliport FAC4 CITY4 Italy 28/03/2006 01:43:25 +Waypoint NARVA Start User Waypoint N50 29.556958191 E12 06.326884143 391 m Symbol Unknown Flag, Green 31/03/2006 21:49:26 http://www.narva-light.de Category 15 + + +Header Name Length Course Waypoints Link + +Route ED_X-LF_X 4087 km 232° true 4 waypoints + +Header Waypoint Name Distance Leg Length Course + +Route Waypoint ED_X 0 m +Route Waypoint GC_X 1936 km 1936 km 227° true +Route Waypoint LI_X 3323 km 1388 km 63° true +Route Waypoint LF_X 4087 km 764 km 300° true + +Route NARVA to Jahnstrasse 394 m 46° true 10 waypoints + +Header Waypoint Name Distance Leg Length Course + +Route Waypoint NARVA 0 m +Route Waypoint 001 2 m 2 m 221° true +Route Waypoint 002 2 m 0 m 0° true +Route Waypoint 003 189 m 188 m 353° true +Route Waypoint 004 274 m 85 m 126° true +Route Waypoint Liebknechtstrasse 274 m 0 m 298° true +Route Waypoint 005 274 m 0 m 118° true +Route Waypoint 006 362 m 88 m 126° true +Route Waypoint 007 393 m 32 m 6° true +Route Waypoint Jahnstrasse 394 m 1 m 351° true + + +Header Name Start Time Elapsed Time Length Average Speed Link + +Track ACTIVE LOG 006 01/05/2005 15:02:47 0:33:09 653 m 1.2 kph + +Header Position Time Altitude Depth Leg Length Leg Time Leg Speed Leg Course + +Trackpoint N51 18.776294924 E12 24.789910130 01/05/2005 15:02:47 161 m 0.0 m +Trackpoint N51 18.772960603 E12 24.794909097 01/05/2005 15:03:25 154 m 0.0 m 8 m 0:00:38 0.8 kph 137° true +Trackpoint N51 18.771295957 E12 24.794909097 01/05/2005 15:03:39 148 m 0.0 m 3 m 0:00:14 0.8 kph 180° true +Trackpoint N51 18.769626282 E12 24.798243418 01/05/2005 15:04:16 139 m 0.0 m 5 m 0:00:37 0.5 kph 129° true +Trackpoint N51 18.769626282 E12 24.796573743 01/05/2005 15:05:02 145 m 0.0 m 2 m 0:00:46 0.2 kph 270° true +Trackpoint N51 18.769626282 E12 24.798243418 01/05/2005 15:05:45 134 m 0.0 m 2 m 0:00:43 0.2 kph 90° true +Trackpoint N51 18.766296990 E12 24.799908064 01/05/2005 15:06:44 131 m 0.0 m 6 m 0:00:59 0.4 kph 163° true +Trackpoint N51 18.766296990 E12 24.799908064 01/05/2005 15:07:50 130 m 0.0 m 0 m 0:01:06 0 kph 0° true +Trackpoint N51 18.764627315 E12 24.799908064 01/05/2005 15:08:19 132 m 0.0 m 3 m 0:00:29 0.4 kph 180° true +Trackpoint N51 18.767961636 E12 24.798243418 01/05/2005 15:11:16 144 m 0.0 m 6 m 0:02:57 0.1 kph 343° true +Trackpoint N51 18.774630278 E12 24.806576706 01/05/2005 15:12:34 147 m 0.0 m 16 m 0:01:18 0.7 kph 38° true +Trackpoint N51 18.779629245 E12 24.828242250 01/05/2005 15:13:18 145 m 0.0 m 27 m 0:00:44 2 kph 70° true +Trackpoint N51 18.782963566 E12 24.828242250 01/05/2005 15:13:27 145 m 0.0 m 6 m 0:00:09 2 kph 0° true +Trackpoint N51 18.782963566 E12 24.829906896 01/05/2005 15:13:37 135 m 0.0 m 2 m 0:00:10 0.7 kph 90° true +Trackpoint N51 18.786292858 E12 24.829906896 01/05/2005 15:13:46 135 m 0.0 m 6 m 0:00:09 2 kph 0° true +Trackpoint N51 18.792961501 E12 24.833241217 01/05/2005 15:14:03 136 m 0.0 m 13 m 0:00:17 3 kph 17° true +Trackpoint N51 18.797960468 E12 24.838240184 01/05/2005 15:14:16 135 m 0.0 m 11 m 0:00:13 3 kph 32° true +Trackpoint N51 18.796295822 E12 24.843239151 01/05/2005 15:14:26 139 m 0.0 m 7 m 0:00:10 2 kph 118° true +Trackpoint N51 18.796295822 E12 24.846573472 01/05/2005 15:14:30 139 m 0.0 m 4 m 0:00:04 3 kph 90° true +Trackpoint N51 18.782963566 E12 24.876572303 01/05/2005 15:15:06 141 m 0.0 m 43 m 0:00:36 4 kph 125° true +Trackpoint N51 18.777959570 E12 24.889909588 01/05/2005 15:15:27 140 m 0.0 m 18 m 0:00:21 3 kph 121° true +Trackpoint N51 18.774630278 E12 24.896573201 01/05/2005 15:15:39 140 m 0.0 m 10 m 0:00:12 3 kph 129° true +Trackpoint N51 18.776294924 E12 24.898242876 01/05/2005 15:25:31 152 m 0.0 m 4 m 0:09:52 0.0 kph 32° true +Trackpoint N51 18.776294924 E12 24.898242876 01/05/2005 15:25:40 152 m 0.0 m 0 m 0:00:09 0 kph 0° true +Trackpoint N51 18.777959570 E12 24.896573201 01/05/2005 15:29:18 155 m 0.0 m 4 m 0:03:38 0.1 kph 328° true +Trackpoint N51 18.789627180 E12 24.874907658 01/05/2005 15:30:30 149 m 0.0 m 33 m 0:01:12 2 kph 311° true +Trackpoint N51 18.789627180 E12 24.873243012 01/05/2005 15:30:37 150 m 0.0 m 2 m 0:00:07 1.0 kph 270° true +Trackpoint N51 18.789627180 E12 24.866574369 01/05/2005 15:30:47 151 m 0.0 m 8 m 0:00:10 3 kph 270° true +Trackpoint N51 18.789627180 E12 24.863240048 01/05/2005 15:30:48 151 m 0.0 m 4 m 0:00:01 14 kph 270° true +Trackpoint N51 18.799630143 E12 24.834905863 01/05/2005 15:30:52 150 m 0.0 m 38 m 0:00:04 34 kph 299° true +Trackpoint N51 18.821295686 E12 24.799908064 01/05/2005 15:30:57 150 m 0.0 m 57 m 0:00:05 41 kph 315° true +Trackpoint N51 18.839626908 E12 24.771573879 01/05/2005 15:31:03 150 m 0.0 m 47 m 0:00:06 28 kph 316° true +Trackpoint N51 18.852959163 E12 24.749908336 01/05/2005 15:31:10 150 m 0.0 m 35 m 0:00:07 18 kph 315° true +Trackpoint N51 18.877959028 E12 24.573239610 01/05/2005 15:32:38 143 m 0.0 m 210 m 0:01:28 9 kph 283° true +Trackpoint N51 18.877959028 E12 24.569910318 01/05/2005 15:32:45 141 m 0.0 m 4 m 0:00:07 2 kph 270° true +Trackpoint N51 18.877959028 E12 24.569910318 01/05/2005 15:33:17 143 m 0.0 m 0 m 0:00:32 0 kph 0° true +Trackpoint N51 18.877959028 E12 24.566575997 01/05/2005 15:33:42 139 m 0.0 m 4 m 0:00:25 0.6 kph 270° true +Trackpoint N51 18.877959028 E12 24.561572000 01/05/2005 15:33:54 139 m 0.0 m 6 m 0:00:12 2 kph 270° true +Trackpoint N51 18.877959028 E12 24.561572000 01/05/2005 15:34:04 138 m 0.0 m 0 m 0:00:10 0 kph 0° true +Trackpoint N51 18.877959028 E12 24.561572000 01/05/2005 15:34:20 139 m 0.0 m 0 m 0:00:16 0 kph 0° true +Trackpoint N51 18.877959028 E12 24.561572000 01/05/2005 15:35:45 144 m 0.0 m 0 m 0:01:25 0 kph 0° true +Trackpoint N51 18.877959028 E12 24.561572000 01/05/2005 15:35:56 145 m 0.0 m 0 m 0:00:11 0 kph 0° true diff --git a/gpsbabel/reference/gdb-sample2.gdb b/gpsbabel/reference/gdb-sample2.gdb new file mode 100644 index 0000000000000000000000000000000000000000..670d0c59ac821a62b1d5c313838b9b6be08b665a GIT binary patch literal 6314 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 B8D#(f literal 0 HcmV?d00001 -- 2.30.2